package com.meatball.authorization.service.impl

import com.meatball.authorization.config.JwtComponent
import com.meatball.authorization.data.dto.LoginDto
import com.meatball.authorization.data.dto.LoginResponse
import com.meatball.authorization.data.dto.RegisterParams
import com.meatball.authorization.data.dto.TokenDetailDto
import com.meatball.authorization.data.entity.RefreshTokenEntity
import com.meatball.authorization.data.entity.UserEntity
import com.meatball.authorization.repository.RefreshTokenRepository
import com.meatball.authorization.repository.UserRepository
import com.meatball.authorization.service.AuthorizationService
import com.meatball.core.config.TOKEN
import com.meatball.core.exception.InvalidTokenException
import com.meatball.core.exception.TokenExpiredException
import org.springframework.data.redis.core.StringRedisTemplate
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service
import java.time.Instant
import java.util.concurrent.TimeUnit

/**
 * TODO 权限管理服务实现类
 *
 * @author 张翔宇
 * @since 2024年3月19日
 */
@Service
class AuthorizationServiceImpl(
        private val userRepository: UserRepository,
        private val refreshTokenRepository: RefreshTokenRepository,
        private val passwordEncoder: PasswordEncoder,
        private val jwtService: JwtComponent,
        private val authenticationManager: AuthenticationManager,
        private val stringRedisTemplate: StringRedisTemplate,
) : AuthorizationService {
    override fun register(params: RegisterParams) {
        // 判断用户是否存在
        userRepository.findByUsername(params.username) ?: throw IllegalArgumentException("用户名已经存在。")
        userRepository.save(UserEntity(
                username = params.username,
                password = passwordEncoder.encode(params.password),
                realName = params.realName,
                avatar = params.avatar
        ))
    }

    /**
     * TODO 登陆
     *
     * @param dto
     * @return
     */
    override fun login(dto: LoginDto): LoginResponse {
        // 通过Spring Security 认证管理器进行认证
        // 如果认证失败会抛出异常 eg:BadCredentialsException 密码错误 UsernameNotFoundException 用户不存在
        authenticationManager.authenticate(
                UsernamePasswordAuthenticationToken(
                        dto.username,
                        dto.password
                )
        )
        userRepository.findByUsername(dto.username) ?.let {
            // 生成token
            val token = jwtService.generateToken(it)
            // 生成刷新token
            val refreshToken = jwtService.generateRefreshToken(it)
            saveToken(token, refreshToken, it)
            // 返回数据
            return LoginResponse(
                username = it.username,
                realName = it.realName,
                token = token.token,
                expiration = token.expiration / 1000
            )
        }
        throw IllegalArgumentException("登陆失败")
    }

    /**
     * TODO 刷新令牌
     *
     * @param refreshToken
     */
    override fun refreshToken(refreshToken: String) : LoginResponse {
        if (!refreshToken.startsWith("Bearer ")) {
            throw InvalidTokenException("非法的令牌格式")
        }
        val tokenEntity = refreshTokenRepository.findByRefreshToken(refreshToken)
        val user = tokenEntity.user ?: throw InvalidTokenException("无效的令牌信息，请重新登陆。")
//        if (calculateTimeDifference(tokenEntity.time) > tokenEntity.refreshExpiration) {
//            throw IllegalArgumentException("刷新令牌已经过期。")
//        }
        // 验证token是否有效
        if (jwtService.isTokenValid(refreshToken, user)) {
            // 生成token
            val token = jwtService.generateToken(user)
            // 生成刷新token
            val refresh = jwtService.generateRefreshToken(user)
            saveToken(token, refresh, user)
            // 返回数据
            return LoginResponse(
                    username = user.username,
                    realName = user.realName,
                    token = token.token,
                    expiration = token.expiration
            )
        }
        throw TokenExpiredException("刷新令牌已经过期。")
    }

    private fun saveToken(token: TokenDetailDto, refreshToken: TokenDetailDto, user: UserEntity) {
        // 保存token到redis
        stringRedisTemplate.opsForValue().set(TOKEN + user.username, token.token, token.expiration, TimeUnit.MILLISECONDS)
        // 保存刷新token到数据库
        refreshTokenRepository.findByUser_Id(user.id) ?.let {
            it.refreshToken = refreshToken.token
            it.refreshExpiration = refreshToken.expiration
            it.time = Instant.now()
            refreshTokenRepository.save(it)
            return
        }
        refreshTokenRepository.save(RefreshTokenEntity(
            refreshToken = refreshToken.token,
            refreshExpiration = refreshToken.expiration,
            time = Instant.now(),
            user = user
        ))
    }
}